<?php
namespace PsiFramework\Classes\Util\HTTP;

/**
 * PsiFramework
 * Php Simple Framework
 *
 * @author Rémi San
 * @version beta
 */


/**
 * The HTTPRequest class allows to launch HTTP requests
 * inside a page and be used as a proxy or to include
 * dynamic generated pages inside your own page.
 *
 * @package classes.util
 */
class HTTPRequest
{

    /**
     * @var string    the "GET" request type
     */
    public static /*string*/ $get = "GET" ;
    /**
     * @var string    the "POST" request type
     */
    public static /*string*/ $post = "POST" ;

    protected /*URI*/      $_uri ;
    protected /*array*/    $_parameters ;
    protected /*int*/      $_timeout ;

    /**
     * HTTPRequest constructor
     *
     * @param $uriString  the uri that must be called
     * @param $parameters the array of parameters formatted
     *                    as $tab["parameterName"] = $parameter
     * @param $timeout    the timeout of the request
     */
    public function __construct(
        /*string*/ $uriString,
        /*array*/ $parameters=array(),
        /*int*/ $timeout=30
    )
    {

        $this->_uri = parse_url($uriString);
        $this->_uri['protocol'] = '';
        if (array_key_exists('scheme', $this->_uri) && $this->_uri['scheme'] == 'https') {
            $this->_uri['protocol'] = 'ssl://';
            $this->_uri['port'] = 443 ;
        } else if (!array_key_exists('port', $this->_uri)) {
            $this->_uri['port'] = 80 ;
        }
        $this->_parameters = $parameters ;
        $this->_timeout = $timeout ;
    }

    /**
     * This function allows to add a parameter to the request
     *
     * @param $paramName    the name of the parameter
     * @param $paramValue    the value of the parameter
     */
    public function addParameter(/*string*/ $paramName, /*string*/ $paramValue)
    {
        $this->_parameters[$paramName] = $paramValue ;
    }
    
    /**
     * This function inits the parameters
     *
     * @param array $params the parameters
     */
    public function setParameters(/*array*/ $params)
    {
        $this->_parameters = $params ;
    }
    
    /**
     * Calculates a securityKey
     * 
     * @param array  $params   the params to calculate
     * @param string $password the password
     */
    public static function calculateSecurityKey(/*array*/ $params, /*string*/ $password)
    {
        ksort($params);
        $hashableString = '';
        
        foreach ($params as $key=>$value) {
            if (!isNullOrEmptyString($value)) {
                $hashableString .= strtoupper($key).'='.$value.$password;
            }
        }
        
        return sha1($hashableString);
    }
    
    /**
     * Adds a security key to the request
     * 
     * @param string $securityKeyParameterName the name of the parameter to add to the request
     * @param string $password the password
     */
    public function addSecurityKey(/*string*/ $securityKeyParameterName, /*string*/ $password)
    {
        $this->addParameter($securityKeyParameterName, self::calculateSecurityKey($this->_parameters, $password));
    }

    /**
     * This function executes the built HTTP request
     *
     * @param $requestType    the request type (POST or GET)
     * @return string
     */
    public function execute(/*string*/ $requestType, &$contentType=null)
    {

        $return = "" ;

        if ($requestType == self::$get || $requestType == self::$post) {

            $args = http_build_query($this->_parameters);

            $fp = fsockopen(
                $this->_uri['protocol'].$this->_uri['host'], 
                $this->_uri['port'], 
                $errno, 
                $errstr, 
                $this->_timeout
            );
            if (!$fp) {

                throw new \Exception("$errstr ($errno)");

            } else {

                $path = '/' ;
                if (isset($this->_uri['path'])) {
                    $path = $this->_uri['path'] ;
                }

                $query = '' ;
                if (isset($this->_uri['query'])
                    &&!isNullOrEmptyString($this->_uri['query'])
                ) {
                    $query = '?' . $this->_uri['query'] ;
                }

                $request  = $requestType . " " . $path.$query . " HTTP/1.1\n";
                $request .= "Host: " . $this->_uri['host'] . "\n";
                $request .= "Connection: Close\r\n" ;
                $request .= "Content-type: application/x-www-form-urlencoded\n";
                $request .= "Content-length: " . strlen($args) . "\n\n";
                $request .= $args . "\n";
                
                fwrite($fp, $request);

                $response = "" ;
                while (!feof($fp)) {
                    $response .= fgets($fp, 512);
                }
                fclose($fp);
                
                $return = $this->processResponse($response);
                $contentType = $this->getContentType($response);
            }

        } else {

            $return = "Invalid Request !" ;

        }

        return $return ;

    }

    public static function unchunk($data)
    {
        $fp = 0;
        $outData = "";
        while ($fp < strlen($data)) {
            $rawnum = substr($data, $fp, strpos(substr($data, $fp), "\r\n") + 2);
            $num = hexdec(trim($rawnum));
            $fp += strlen($rawnum);
            $chunk = substr($data, $fp, $num);
            $outData .= $chunk;
            $fp += strlen($chunk);
        }
        return $outData;
    } 
    
    /**
     * This function parses the response passed as an argument
     *
     * @param $response    the response to parse
     * @return string
     */
    protected function processResponse(/*string*/ $data)
    {
        $return = "" ;
        
        $chunks = explode("\r\n\r\n", trim($data));
        $errCode = null;
        if ($this->checkHeaders($chunks[0], $errCode)) {
            $response = trim(substr($data, strpos($data, "\r\n\r\n") + 4));
            if (strpos(strtolower($chunks[0]), "transfer-encoding: chunked") !== FALSE) {
                $response = self::unchunk($response);
            }
            return $response;
        } else {
            throw new \Exception("Bad response : ".$errCode);
        }
    }
    
    protected function getContentType(/*string*/ $data)
    {
        $pos = strpos(strtolower($data), 'content-type');
        $endPos = strpos($data, "\n", $pos);
        $ct = substr($data, $pos, $endPos-$pos);
        
        return trim($ct);
    }

    /**
     * This function checks if the response headers are
     * valid headers of a succeeded request
     *
     * @param $headers    the headers to check
     * @return boolean
     */
    protected function checkHeaders(/*string*/ $headers, &$errorCode)
    {

        $startPos = strpos(mb_strtoupper($headers, 'utf-8'), "HTTP/1.") + 9 ;
        $code = substr($headers, $startPos, 3);
        $errorCode = $code;

        return ($code == "100" || $code == "200");

    }

}

